package com.study.security.config;

import com.study.constant.CommonConstant.PageUrl;
import com.study.constant.CommonConstant.Role;
import com.study.security.handler.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

@Slf4j
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**/*.js","/**/*.html"); // 允许静态资源不经过认证
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 异常情况处理
        http.exceptionHandling((exceptionHandling) ->
                exceptionHandling
                        .accessDeniedHandler(new MyAccessDeniedHandler()) // 设置没有权限访问后续处理
                        .authenticationEntryPoint(new NotLoginAuthenticationEntryPoint()) // 设置用户未登录时访问需要登录的接口处理逻辑
        );

        // 表单登录
        http.formLogin()
                // 登录时传递用户名和密码使用的参数名
                .usernameParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY)
                .passwordParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY)

                // 修改默认的登录页为login.html，他会自动去根路径static文件夹下寻找login.html
                .loginPage(PageUrl.LOGIN_PAGE)

                // 设置登录接口地址，这个接口不是真实存在的，还是用的security给我们提供的，之所以要有这个配置，是login.html当中form表单提交的地址我们设置的是这个
                .loginProcessingUrl(PageUrl.LOGIN_URL)

                .successHandler(new MyAuthenticationSuccessHandler()) // 登录成功之后的处理器

                .failureHandler(new MyAuthenticationFailureHandler()) // 登录失败之后的处理器

                .permitAll(); // permitAll中文意思是许可所有的：所有的都遵循上面的配置的意思

        // 接口权限认证
        http.authorizeRequests()
                // 只要登录就能访问
                .antMatchers(PageUrl.ONLY_NEED_AUTH_ARR).authenticated()

                // oauth接口访问限制
                .antMatchers(PageUrl.OAUTH_URL_MATCHER).hasAnyRole(Role.OAUTH_ROLE)

                // 未被前面antMatchers匹配的任何请求都限制访问
                .anyRequest().denyAll();


        // 退出,这里的/login/loginOut的请求是和前端的接口约定，是security给我们提供的,退出成功后跳转登录页/login.html 或者 执行Handler
        http.logout().logoutUrl(PageUrl.LOGIN_OUT_URL)
                .logoutSuccessHandler(new MyLogoutSuccessHandler())
                //.logoutSuccessUrl(PageUrl.LOGIN_PAGE)
                .permitAll();


        http.rememberMe() // 设置记住我
                .tokenRepository(persistentTokenRepository) // 配置token存储库
                // 设置有效时长180秒，默认 2 周时间。
                .tokenValiditySeconds(180)
                .userDetailsService(userDetailsService);

        http.sessionManagement() // 回话管理器
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 指定session创建策略

                .maximumSessions(1) // 同一个用户的最大会话数（同一个账号允许同时登录多个客户端的数量）
                .maxSessionsPreventsLogin(false) // 达到回话限制时的处理，true：当前会话无法登录成功  false：之前的会话失效
                .sessionRegistry(sessionRegistry); // 指定session注册器

        // 关闭 csrf
        http.csrf().disable();


        // https://blog.csdn.net/fd2025/article/details/124491570
        http.headers().frameOptions().sameOrigin(); // 页面只能被本站页面嵌入到iframe或者frame中
    }

    /**
     * 用户类信息
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 告诉SpringSecurity 我们要使用自己定义的userDetailsService来通过username来获取主体，
        // 并且使用了BCryptPasswordEncoder加密进行密码比较
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }
}
   